package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.promotion.enums.CouponStatus.*;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author huanmo
 * @since 2025-09-26
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService scopeService;

    private final IExchangeCodeService codeService;

    private final IUserCouponService userCouponService;

    private final StringRedisTemplate redisTemplate;

    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        // 1.保存优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        // 1.2.保存
        save(coupon);
        if (!dto.getSpecific()) {
            // 没有范围限定
            return;
        }
        Long couponId = coupon.getId();
        // 2.保存限定范围
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        // 2.1.转换PO
        List<CouponScope> list = scopes.stream()
                .map(bizId -> new CouponScope()
                        .setBizId(bizId)
                        .setCouponId(couponId)
                        .setType(1))
                .collect(Collectors.toList());
        // 2.2.保存
        scopeService.saveBatch(list);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponByPage(CouponQuery query) {
        Integer status = query.getStatus();
        String name = query.getName();
        Integer type = query.getType();
        // 1.分页查询
        Page<Coupon> page = lambdaQuery()
                .eq(type != null, Coupon::getDiscountType, type)
                .eq(status != null, Coupon::getStatus, status)
                .like(StringUtils.isNotBlank(name), Coupon::getName, name)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        // 2.处理VO
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        List<CouponPageVO> list = BeanUtils.copyList(records, CouponPageVO.class);
        // 3.返回
        return PageDTO.of(page, list);
    }

    @Override
    @Transactional
    public void beginIssue(CouponIssueFormDTO dto) {
        // 1.查询优惠券
        Coupon coupon = getById(dto.getId());
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在！");
        }
        // 2.判断优惠券状态，是否是暂停或待发放
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != PAUSE) {
            throw new BizIllegalException("优惠券状态错误！");
        }
        // 3.判断是否是立刻发放
        LocalDateTime issueBeginTime = dto.getIssueBeginTime();
        LocalDateTime now = LocalDateTime.now();
        boolean isBegin = issueBeginTime == null || !issueBeginTime.isAfter(now);
        // 4.更新优惠券
        // 4.1.拷贝属性到PO
        Coupon c = BeanUtils.copyBean(dto, Coupon.class);
        // 4.2.更新状态
        if (isBegin) {
            c.setStatus(ISSUING);
            c.setIssueBeginTime(now);
        } else {
            c.setStatus(UN_ISSUE);
        }
        // 4.3.写入数据库
        updateById(c);
        // 5.添加缓存
        if (isBegin) {
            coupon.setIssueBeginTime(c.getIssueBeginTime());
            coupon.setIssueEndTime(c.getIssueEndTime());
            cacheCouponInfo(coupon);
        }
        // 6.判断是否需要生成兑换码，优惠券类型必须是兑换码，优惠券状态必须是待发放
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(c.getIssueEndTime());
            codeService.asyncGenerateCode(coupon);
        }
    }

    private void cacheCouponInfo(Coupon coupon) {
        // 1.组织数据
        Map<String, String> map = new HashMap<>(4);
        map.put("issueBeginTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueBeginTime())));
        map.put("issueEndTime", String.valueOf(DateUtils.toEpochMilli(coupon.getIssueEndTime())));
        map.put("totalNum", String.valueOf(coupon.getTotalNum()));
        map.put("userLimit", String.valueOf(coupon.getUserLimit()));
        // 2.写缓存
        redisTemplate.opsForHash().putAll(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), map);
    }

    @Override
    @Transactional
    public void updateCoupon(Long id, CouponFormDTO dto) {
        // 1.修改优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        coupon.setId(id);
        // 1.2.修改
        updateById(coupon);
        // 2.处理限定范围
        if (!dto.getSpecific()) {
            // 没有范围限定，删除已有的限定范围
            scopeService.lambdaUpdate()
                    .eq(CouponScope::getCouponId, id)
                    .remove();
            return;
        }
        // 3.有范围限定，验证参数
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BadRequestException("限定范围不能为空");
        }
        // 4.删除旧的限定范围
        scopeService.lambdaUpdate()
                .eq(CouponScope::getCouponId, id)
                .remove();
        // 5.添加新的限定范围
        List<CouponScope> scopeList = scopes.stream()
                .map(bizId -> new CouponScope()
                        .setBizId(bizId)
                        .setCouponId(id)
                        .setType(1))
                .collect(Collectors.toList());
        scopeService.saveBatch(scopeList);
    }

    @Override
    @Transactional
    public void deleteCoupon(Long id) {
        // 1.获取优惠券
        Coupon coupon = baseMapper.selectById(id);
        if (coupon == null) {
            log.warn("要删除的优惠券不存在，ID: {}", id);
            return;
        }
        // 2.先删除限定范围（如果有）
        if (Boolean.TRUE.equals(coupon.getSpecific())) {
            boolean deleted = scopeService.lambdaUpdate()
                    .eq(CouponScope::getCouponId, id)
                    .remove();
            log.info("删除优惠券ID: {} 的限定范围，结果: {}", id, deleted);
        }
        // 3.删除兑换码（如果有）
        if (ObtainType.ISSUE.equals(coupon.getObtainWay())) {
            boolean deleted = codeService.lambdaUpdate()
                    .eq(ExchangeCode::getExchangeTargetId, id)
                    .remove();
            log.info("删除优惠券ID: {} 的兑换码，结果: {}", id, deleted);
        }
        // 4.删除优惠券
        boolean success = removeById(id);
        if (success) {
            log.info("成功删除优惠券，ID: {}", id);
        } else {
            log.error("删除优惠券失败，ID: {}", id);
            throw new RuntimeException("删除优惠券失败");
        }
    }

    /**
     * 批量删除优惠券
     */
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteCoupons(List<Long> ids) {
        if (CollUtils.isEmpty(ids)) {
            return;
        }
        log.info("批量删除优惠券，IDs: {}", ids);
        // 1.查询所有优惠券信息
        List<Coupon> coupons = listByIds(ids);
        if (coupons.size() != ids.size()) {
            log.warn("部分优惠券不存在，查询到{}条，期望{}条", coupons.size(), ids.size());
        }
        Map<Long, Boolean> couponSpecificMap = coupons.stream()
                .collect(Collectors.toMap(Coupon::getId, Coupon::getSpecific));
        // 2.批量删除限定范围
        List<Long> hasSpecificIds = couponSpecificMap.entrySet().stream()
                .filter(entry -> Boolean.TRUE.equals(entry.getValue()))
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
        if (CollUtils.isNotEmpty(hasSpecificIds)) {
            boolean scopeDeleted = scopeService.lambdaUpdate()
                    .in(CouponScope::getCouponId, hasSpecificIds)
                    .remove();
            log.info("批量删除优惠券范围，涉及{}个优惠券，结果: {}", hasSpecificIds.size(), scopeDeleted);
        }
        // 3.批量删除兑换码

        // 4.批量删除优惠券
        boolean success = removeByIds(ids);
        if (!success) {
            throw new RuntimeException("批量删除优惠券失败");
        }
        log.info("批量删除优惠券成功，数量: {}", ids.size());
    }

    @Override
    public CouponDetailVO getCouponById(Long id) {
        // 查询优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            log.warn("查询的优惠券不存在，ID: {}", id);
            return null;
        }
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        // 处理特殊字段：优惠券范围
        if (Boolean.TRUE.equals(coupon.getSpecific())) {
            List<CouponScopeVO> scopeVos = scopeService.queryCouponScopes(coupon.getId());
            vo.setScopes(scopeVos);
        } else {
            vo.setScopes(Collections.emptyList());
        }
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void startIssuePromotion(List<Coupon> coupons, LocalDateTime now) {
        // 1.提取ID列表
        List<Long> couponIds = coupons.stream()
                .map(Coupon::getId)
                .collect(Collectors.toList());
        // 2.更新优惠券状态
        boolean updateSuccess = lambdaUpdate()
                .in(Coupon::getId, couponIds)
                .set(Coupon::getStatus, ISSUING)
                .set(Coupon::getUpdateTime, now)
                .update();
        if (!updateSuccess) {
            throw new RuntimeException("优惠券状态更新失败");
        }
        // 3.查询优惠券是否为兑换码类型
        List<Coupon> codeCoupons = coupons.stream()
                .filter(coupon -> ObtainType.ISSUE.equals(coupon.getObtainWay()))
                .collect(Collectors.toList());
        // 4.批量发放兑换码
        if (!codeCoupons.isEmpty()) {
            codeService.asyncBatchGenerateCodes(codeCoupons);
            log.info("已提交批量发行兑换码任务，优惠券数量：{}", coupons.size());
        }
        // 5.添加缓存
        coupons.forEach(this::cacheCouponInfo);
        log.debug("优惠券发放促销完成，共处理{}张优惠券，其中{}张需要生成兑换码",
                coupons.size(), codeCoupons.size());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void endIssuePromotion(List<Coupon> coupons, LocalDateTime now) {
        // 1.提取ID列表
        List<Long> couponIds = coupons.stream()
                .map(Coupon::getId)
                .collect(Collectors.toList());
        // 1.更新优惠券状态
        boolean updateSuccess = lambdaUpdate()
                .in(Coupon::getId, couponIds)
                .set(Coupon::getStatus, FINISHED)
                .set(Coupon::getUpdateTime, now)
                .update();
        if (!updateSuccess) {
            log.error("优惠券状态更新失败，couponIds: {}", couponIds);
            throw new RuntimeException("优惠券状态更新失败");
        }
        // 2.查询优惠券是否为兑换码类型
        List<Coupon> codeCoupons = coupons.stream()
                .filter(coupon -> ObtainType.ISSUE.equals(coupon.getObtainWay()))
                .collect(Collectors.toList());
        // 3.批量暂停兑换码
        if (!codeCoupons.isEmpty()) {
            codeService.asyncBatchPauseCodes(codeCoupons);
            log.info("已提交批量暂停兑换码任务，优惠券数量：{}", codeCoupons.size());
        }
        // 4.删除缓存
        couponIds.forEach(couponId -> redisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId));
        log.debug("优惠券停止促销完成，共处理{}张优惠券，其中{}张需要暂停兑换码",
                coupons.size(), codeCoupons.size());
    }

    @Override
    @Transactional
    public void pauseCoupon(Long id) {
        LocalDateTime now = LocalDateTime.now();
        // 1.查询优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            log.warn("要暂停的优惠券不存在，ID:{}", id);
            return;
        }
        if (!coupon.getStatus().equals(ISSUING)) {
            log.warn("优惠券{}的状态为{}，无需暂停", id, coupon.getStatus());
            return;
        }
        // 2.暂停优惠券
        lambdaUpdate()
                .eq(Coupon::getId, id)
                .set(Coupon::getStatus, PAUSE)
                .set(Coupon::getUpdateTime, now)
                .update();
        // 3.暂停兑换码
        if (coupon.getObtainWay().equals(ObtainType.ISSUE)) {
            codeService.asyncPauseCode(coupon);
        }
        // 4.删除缓存
        redisTemplate.delete(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id);
    }

    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1.查询发放中的优惠券列表
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }
        // 2.统计当前用户已经领取的优惠券的信息
        List<Long> couponIds = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        // 2.1.查询当前用户已经领取的优惠券的数据
        // 先从数据库全部取出来，再分别统计。这样只需访问一次数据库
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        // 2.2.统计当前用户对优惠券的已经领取数量
        // key:couponId  value:已领取的该种类优惠券的数量
        Map<Long, Long> issuedMap = userCoupons.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 2.3.统计当前用户对优惠券的已经领取并且未使用的数量
        // key:couponId  value:剩余可用的该种类优惠券的数量
        Map<Long, Long> unusedMap = userCoupons.stream()
                .filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 3.封装VO结果
        List<CouponVO> list = new ArrayList<>(coupons.size());
        for (Coupon c : coupons) {
            // 3.1.拷贝PO属性到VO
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);
            list.add(vo);
            // 3.2.是否可以领取：已经被领取的数量 < 优惠券总数量 && 当前用户已经领取的数量 < 每人限领数量
            vo.setAvailable(
                    c.getIssueNum() < c.getTotalNum()
                            && issuedMap.getOrDefault(c.getId(), 0L) < c.getUserLimit()
            );
            // 3.3.是否可以使用：当前用户已经领取并且未使用的优惠券数量 > 0
            vo.setReceived(unusedMap.getOrDefault(c.getId(), 0L) > 0);
        }
        return list;
    }
}
